package com.leizhi.demo.monitor.achieve;

import com.leizhi.demo.monitor.cache.DeviceCache;
import com.leizhi.demo.monitor.pojo.*;
import com.leizhi.demo.monitor.utils.SipContentHelperUtil;
import com.leizhi.demo.monitor.utils.SipUtil;
import io.netty.channel.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import javax.sip.Dialog;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.leizhi.demo.monitor.utils.SipConstantUtil.SESSION_NAME_PLAY;
import static com.leizhi.demo.monitor.utils.SipConstantUtil.TCP;

/**
 * @Description 正在推的流
 * @Version 1.0.0-SNAPSHOT
 * @Author leizhi
 * @Date 2025/5/10 9:14
 */
public class PushStreamService {

    private static final Logger logger = LoggerFactory.getLogger(PushStreamService.class);
    private static long lastTimestamp = System.currentTimeMillis();
    private static long counter = 0;

    /* 存储每个设备推流的信息 */
    private final Map<String, PushStreamInfo> pushStreamMap;
    /* 存储每个设备的推流进程 */
    private final Map<Long, PushStreamProcessInfo> pushStreamProcessMap;

    @Autowired
    private SipDispChange sipDispChange;
    @Autowired
    private DeviceCache deviceCache;
    @Autowired
    private SipService sipService;
    @Autowired
    private RtpUsablePort rtpUsablePort;


    public PushStreamService() {
        this.pushStreamMap = new HashMap<>();
        this.pushStreamProcessMap = new HashMap<>();
    }

    /* 包装实时流媒体请求 */
    public PushStreamInfo sendInvite(String deviceId) {
        Device device = deviceCache.select(deviceId);
        if (device == null) {
            throw new RuntimeException("设备未注册");
        }

        Map<String, DeviceChannel> channelMap = device.getChannelMap();
        if (channelMap == null || !channelMap.containsKey(device.getAcquiesceChannel())) {
            throw new RuntimeException("通道未注册");
        }

        boolean isFlowTcp = device.getFlowProtocol().equals(TCP);
        Integer port = isFlowTcp ? sipDispChange.getTcpRtpPort() : rtpUsablePort.usable();
        try {
            PushStreamInfo stream = pushStreamReady(device);
            String content = SipContentHelperUtil.generateRealTimeStreamInviteContent(
                    sipDispChange.getSipId(),
                    sipDispChange.getSipIp(),
                    port,
                    stream.getSsrc()
            );

            /* 下发设备推送媒体流命令 */
            sipService.sendInvite(device, stream.getCallId(), content);
            SyncFuture<?> receive = receiveRespondCheck(stream.getCallId());
            Dialog response = (Dialog) receive.get(3, TimeUnit.SECONDS);
            if (response == null) {
                throw new RuntimeException("命令无响应");
            }
            stream.setDialog(response);

            // 推流进程未开启，则开启推流进程
            if (!stream.getIsPushStream()) {
                PushStreamProcessInfo process = new PushStreamProcessInfo(deviceId, stream.getSsrc(), device.getIsCloud(), port);
                if (!isFlowTcp) {
                    Channel channel = RtpService.getInstance().udpStart(stream.getSsrc(), port);
                    process.setChannel(channel);
                }

                pushStreamMap.put(deviceId, stream);
                pushStreamProcessMap.put(stream.getSsrc(), process);
                stream.setIsPushStream(true);
            }
            return stream;
        } catch (Exception e) {
            rtpUsablePort.remove(port);
            throw new RuntimeException(e.getMessage());
        }
    }

    /* 关闭指定通道 */
    public void stop(String devId) {
        PushStreamInfo pushStream = pushStreamMap.get(devId);
        if (pushStream == null) {
            return;
        }

        try {
            Long ssrc = pushStream.getSsrc();
            PushStreamProcessInfo process = getProcess(ssrc);
            if (process != null) {
                process.close();
                pushStreamProcessMap.remove(ssrc);
                rtpUsablePort.remove(process.getPort());
            }

            pushStreamMap.remove(devId);
            sipService.sendBye(pushStream);
            pushStream.setIsPushStream(false);
            logger.info("DeviceId-{}：设备连接与 FFmpeg 进程已关闭，", devId);
        } catch (Exception e) {
            logger.error("DeviceId-{}：设备连接与 FFmpeg 进程关闭失败，错误信息：{}", devId, e.getMessage());
        }
    }

    /* 获取推流信息 */
    public PushStreamInfo getPushStream(String devId) {
        return pushStreamMap.get(devId);
    }

    /* 获取推流进程 */
    public PushStreamProcessInfo getProcess(Long ssrc) {
        return pushStreamProcessMap.get(ssrc);
    }

    /* 推流准备 */
    private PushStreamInfo pushStreamReady(Device device) {
        PushStreamInfo pushStream = getPushStream(device.getDeviceId());
        if (pushStream != null) {
            return pushStream;
        }

        /* 生成推流通道信息 */
        Long ssrc = ssrcPlay();    // 需要改成每个设备唯一的SSRC，因为RTP只支持长度为8的SSRC编号。
        String callId = SipUtil.uuid();
        String address = sipDispChange.getPushRtmpAddress() + ssrc;
        String streamName = playStreamName(device.getDeviceId(), device.getAcquiesceChannel());
        return new PushStreamInfo(ssrc, callId, streamName, device.getSignalingProtocol(), address);
    }

    /* 添加同步响应信息 */
    private SyncFuture<?> receiveRespondCheck(String callId) {
        SyncFuture<Dialog> future = new SyncFuture<>();
        sipService.putRespondCheck(callId, future);
        return future;
    }

    /* 生成播放流名称 */
    private String playStreamName(String deviceId, String channelId) {
        if (channelId == null) {
            throw new RuntimeException("通道未注册");
        }

        return deviceId + "_" + channelId + "_" + SESSION_NAME_PLAY;
    }

    /* 生成 SSRC */
    public static synchronized Long ssrcPlay() {
        long timestamp = System.currentTimeMillis();
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("时钟回拨");
        }

        if (timestamp == lastTimestamp) {
            counter++;
        } else {
            counter = 0;
        }

        lastTimestamp = timestamp;
        return ((timestamp % 100000000L + counter) % 100000000L);
    }

}
